/*
 * Copyright (c) 2021-2021. Gardel
 *
 * This file is part of Source2Pdf (https://gitee.com/gardel/source2pdf).
 *
 * Source2Pdf is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Source2Pdf is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Source2Pdf.  If not, see <http://www.gnu.org/licenses/>.
 */

package cn.sunxinao.source2pdf.utils;

import io.github.palexdev.materialfx.controls.MFXIconWrapper;
import java.util.List;
import javafx.event.EventHandler;
import javafx.event.EventTarget;
import javafx.event.EventType;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;

/**
 * Util class to handle window resizing when a stage style set to StageStyle.UNDECORATED.
 * Includes dragging of the stage.
 * Original on 6/13/14.
 * Updated on 8/15/17.
 * Updated on 12/19/19.
 * Updated on 9/23/21.
 *
 * @author Alexander.Berg
 * @author Evgenii Kanivets
 * @author Zachary Perales
 * @author Gardel
 */
public class ResizeHelper {
    private static final List<EventType<MouseEvent>> ALL_TYPES = List.of(
        MouseEvent.MOUSE_MOVED,
        MouseEvent.MOUSE_PRESSED,
        MouseEvent.MOUSE_DRAGGED,
        MouseEvent.MOUSE_EXITED,
        MouseEvent.MOUSE_EXITED_TARGET
    );

    public static void addResizeListener(Stage stage) {
        addResizeListener(stage, 1, 1, Double.MAX_VALUE, Double.MAX_VALUE);
    }

    public static void addResizeListener(Stage stage, double minWidth, double minHeight, double maxWidth, double maxHeight) {
        ResizeListener resizeListener = new ResizeListener(stage);
        EventUtils.addListenerCombine(stage, ALL_TYPES, resizeListener);

        Scene scene = stage.getScene();
        EventUtils.addListenerCombine(scene, ALL_TYPES, resizeListener);

        resizeListener.setMinWidth(minWidth);
        resizeListener.setMinHeight(minHeight);
        resizeListener.setMaxWidth(maxWidth);
        resizeListener.setMaxHeight(maxHeight);

        EventUtils.addListenerDeeply(scene.getRoot(), ALL_TYPES, resizeListener);
    }

    static class ResizeListener implements EventHandler<MouseEvent> {
        private static final int BORDER = 5;
        private final Stage stage;
        private Cursor cursorEvent = Cursor.DEFAULT;
        private boolean resizing = true;
        private double startX = 0;
        private double startY = 0;
        private double screenOffsetX = 0;
        private double screenOffsetY = 0;

        // Max and min sizes for controlled stage
        private double minWidth;
        private double maxWidth;
        private double minHeight;
        private double maxHeight;

        public ResizeListener(Stage stage) {
            this.stage = stage;
        }

        public void setMinWidth(double minWidth) {
            this.minWidth = minWidth;
        }

        public void setMaxWidth(double maxWidth) {
            this.maxWidth = maxWidth;
        }

        public void setMinHeight(double minHeight) {
            this.minHeight = minHeight;
        }

        public void setMaxHeight(double maxHeight) {
            this.maxHeight = maxHeight;
        }

        @Override
        public void handle(MouseEvent mouseEvent) {
            EventType<? extends MouseEvent> mouseEventType = mouseEvent.getEventType();
            EventTarget target = mouseEvent.getTarget();
            Scene scene = stage.getScene();

            double mouseEventX = mouseEvent.getSceneX(),
                mouseEventY = mouseEvent.getSceneY(),
                sceneWidth = scene.getWidth(),
                sceneHeight = scene.getHeight();

            if (MouseEvent.MOUSE_MOVED.equals(mouseEventType) && !stage.isMaximized() && !stage.isFullScreen()) {
                if (mouseEventX < BORDER && mouseEventY < BORDER) {
                    cursorEvent = Cursor.NW_RESIZE;
                } else if (mouseEventX < BORDER && mouseEventY > sceneHeight - BORDER) {
                    cursorEvent = Cursor.SW_RESIZE;
                } else if (mouseEventX > sceneWidth - BORDER && mouseEventY < BORDER) {
                    cursorEvent = Cursor.NE_RESIZE;
                } else if (mouseEventX > sceneWidth - BORDER && mouseEventY > sceneHeight - BORDER) {
                    cursorEvent = Cursor.SE_RESIZE;
                } else if (mouseEventX < BORDER) {
                    cursorEvent = Cursor.W_RESIZE;
                } else if (mouseEventX > sceneWidth - BORDER) {
                    cursorEvent = Cursor.E_RESIZE;
                } else if (mouseEventY < BORDER) {
                    cursorEvent = Cursor.N_RESIZE;
                } else if (mouseEventY > sceneHeight - BORDER) {
                    cursorEvent = Cursor.S_RESIZE;
                } else if (mouseEventY > BORDER && mouseEventX > BORDER &&
                    mouseEventX < sceneWidth - BORDER && mouseEventY < BORDER + 30) {
                    if (target == null) {
                        cursorEvent = Cursor.MOVE;
                    } else if (target instanceof Button || target instanceof MFXIconWrapper) {
                        Node node = ((Node) target);
                        cursorEvent = node.getCursor() == null ? Cursor.DEFAULT : node.getCursor();
                    } else {
                        cursorEvent = Cursor.MOVE;
                    }
                } else {
                    cursorEvent = Cursor.DEFAULT;
                }
                scene.setCursor(cursorEvent);
            } else if (MouseEvent.MOUSE_EXITED.equals(mouseEventType) || MouseEvent.MOUSE_EXITED_TARGET.equals(mouseEventType)) {
                scene.setCursor(Cursor.DEFAULT);
            } else if (MouseEvent.MOUSE_PRESSED.equals(mouseEventType) && mouseEvent.getButton() == MouseButton.PRIMARY) {
                startX = stage.getWidth() - mouseEventX;
                startY = stage.getHeight() - mouseEventY;

                if (Cursor.MOVE.equals(cursorEvent)) {
                    resizing = false;
                    screenOffsetX = stage.getX() - mouseEvent.getScreenX();
                    screenOffsetY = stage.getY() - mouseEvent.getScreenY();
                }
            } else if (MouseEvent.MOUSE_DRAGGED.equals(mouseEventType) && mouseEvent.getButton() == MouseButton.PRIMARY) {
                if (Cursor.MOVE.equals(cursorEvent) && !resizing) {
                    stage.setX(mouseEvent.getScreenX() + screenOffsetX);
                    stage.setY(mouseEvent.getScreenY() + screenOffsetY);
                } else if (!Cursor.DEFAULT.equals(cursorEvent)) {
                    resizing = true;
                    if (!Cursor.W_RESIZE.equals(cursorEvent) && !Cursor.E_RESIZE.equals(cursorEvent)) {
                        double minHeight = stage.getMinHeight() > (BORDER * 2) ? stage.getMinHeight() : (BORDER * 2);
                        if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.N_RESIZE.equals(cursorEvent)
                            || Cursor.NE_RESIZE.equals(cursorEvent)) {
                            if (stage.getHeight() > minHeight || mouseEventY < 0) {
                                setStageHeight(stage.getY() - mouseEvent.getScreenY() + stage.getHeight(), mouseEvent.getScreenY());
                            }
                        } else {
                            if (stage.getHeight() > minHeight || mouseEventY + startY - stage.getHeight() > 0) {
                                setStageHeight(mouseEventY + startY, -1);
                            }
                        }
                    }

                    if (!Cursor.N_RESIZE.equals(cursorEvent) && !Cursor.S_RESIZE.equals(cursorEvent)) {
                        double minWidth = stage.getMinWidth() > (BORDER * 2) ? stage.getMinWidth() : (BORDER * 2);
                        if (Cursor.NW_RESIZE.equals(cursorEvent) || Cursor.W_RESIZE.equals(cursorEvent)
                            || Cursor.SW_RESIZE.equals(cursorEvent)) {
                            if (stage.getWidth() > minWidth || mouseEventX < 0) {
                                setStageWidth(stage.getX() - mouseEvent.getScreenX() + stage.getWidth(), mouseEvent.getScreenX());
                            }
                        } else {
                            if (stage.getWidth() > minWidth || mouseEventX + startX - stage.getWidth() > 0) {
                                setStageWidth(mouseEventX + startX, -1);
                            }
                        }
                    }
                    resizing = false;
                }
            }
        }

        private void setStageWidth(double width, double moveX) {
            width = Math.min(width, maxWidth);
            width = Math.max(width, minWidth);
            if (Math.abs(width - stage.getWidth()) > 0.01d) {
                stage.setWidth(width);
                if ((int) moveX != -1) {
                    stage.setX(moveX);
                }
            }
        }

        private void setStageHeight(double height, double moveY) {
            height = Math.min(height, maxHeight);
            height = Math.max(height, minHeight);
            if (Math.abs(height - stage.getHeight()) > 0.01d) {
                stage.setHeight(height);
                if ((int) moveY != -1) {
                    stage.setY(moveY);
                }
            }
        }
    }
}
